﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using PoolGame2.Controls;
using PoolGame2.Game;

namespace PoolGame2.Physics
{
    class PhysicsDebug : IGameControlListener
    {

        // debug build mode
        private bool cushionEditMode = false;
        private bool fastmove = true;
        private int selectedCushion = 0;
        private int selectedVertex = 0;

        // for drawing circles
        private static int slices = 20;

        private VertexPositionColor[] trackingPointList;
        private int[] trackingListIndices;

        // debug variables
        private VertexPositionColor[] cushionPointList;
        private int[] cushionLineListIndices;

        private VertexPositionColor[] ballPointList;
        private int[] ballLineListIndices;

        private VertexPositionColor[] pocketPointList;
        private int[] pocketLineListIndices;

        private VertexDeclaration vertexDeclaration;
        private BasicEffect basicEffect;

        private PhysicsRuntime runtime;

        public PhysicsDebug(PhysicsRuntime pr)
        {
            this.runtime = pr;
            basicEffect = new BasicEffect(runtime.game.GraphicsDevice, null);
        }

        public void DrawDebug(GameTime gameTime)
        {
            runtime.game.GraphicsDevice.VertexDeclaration = vertexDeclaration;

            basicEffect.World = runtime.table.getWorldMatrix();
            basicEffect.View = runtime.cameraManager.GetCamera().viewMatrix;
            basicEffect.Projection = runtime.cameraManager.GetCamera().projectionMatrix;

            basicEffect.Begin();
            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                DrawCushionData();
                DrawPocketData();
                DrawBallData();
                pass.End();
            }
            basicEffect.End();

            if (cushionEditMode)
            {
                SpriteFont font = runtime.game.fontDatabase.loadFont("Courier New");
                runtime.game.spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
                runtime.game.spriteBatch.DrawString(font, "Cushion Edit mode :: Selected Cushion = " + selectedCushion + ", Selected vertex = " + selectedVertex, new Vector2(300, GameConstants.SCREEN_HEIGHT - 35), Color.Red);
                Cushion cushion = runtime.getState().getCushions()[selectedCushion];
                Vector3 pos = Vector3.Zero;
                switch (selectedVertex) {
                    case 0:
                        pos = cushion.start;
                        break;
                    case 1:
                        pos = cushion.mainStart;
                        break;
                    case 2:
                        pos = cushion.mainEnd;
                        break;
                    case 3:
                        pos = cushion.end;
                        break;

                }
                runtime.game.spriteBatch.DrawString(font, "Cushion Edit mode :: Current Vertex Position = (" + pos.X + "," + pos.Y + "," + pos.Z + ")", new Vector2(300, GameConstants.SCREEN_HEIGHT - 20), Color.Red);
                runtime.game.spriteBatch.End();
            }
        }

        private void DrawCushionData() {
            runtime.game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                 PrimitiveType.LineList,
                 cushionPointList,
                 0,  // vertex buffer offset to add to each element of the index buffer
                 10 * runtime.getState().getCushions().Length,  // number of vertices in pointList
                 cushionLineListIndices,  // the index buffer
                 0,  // first index element to read
                 12 * runtime.getState().getCushions().Length / 2   // number of primitives to draw
             );
        }

        private void DrawPocketData()
        {
            runtime.game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                 PrimitiveType.LineList,
                 pocketPointList,
                 0,  // vertex buffer offset to add to each element of the index buffer
                 slices * runtime.getState().getPockets().Length,  // number of vertices in pointList
                 pocketLineListIndices,  // the index buffer
                 0,  // first index element to read
                 slices * runtime.getState().getPockets().Length   // number of primitives to draw
             );
        }

        private void DrawBallData()
        {
            runtime.game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                 PrimitiveType.LineList,
                 ballPointList,
                 0,  // vertex buffer offset to add to each element of the index buffer
                 slices * runtime.getState().getBalls().Length,  // number of vertices in pointList
                 ballLineListIndices,  // the index buffer
                 0,  // first index element to read
                 slices * runtime.getState().getBalls().Length   // number of primitives to draw
             );
        }

        public void InitializeDebugDrawingElements()
        {
            vertexDeclaration = new VertexDeclaration(runtime.game.GraphicsDevice, VertexPositionColor.VertexElements);

            basicEffect.DiffuseColor = new Vector3(1.0f, 1.0f, 1.0f);

            BuildCushionData();
            BuildPocketData();
            BuildBallData();
        }

        private void BuildCushionData()
        {
            cushionPointList = new VertexPositionColor[runtime.getState().getCushions().Length * 10];
            for (int i = 0; i < cushionPointList.Length; i++)
            {
                cushionPointList[i] = new VertexPositionColor();
                cushionPointList[i].Color = Color.Red;
            }

            int count = 0;
            foreach (Cushion cush in runtime.getState().getCushions())
            {
                if (cush != null)
                {
                    // cushion 
                    cushionPointList[count].Position = cush.start;
                    cushionPointList[count + 1].Position = cush.mainStart;
                    cushionPointList[count + 2].Position = cush.mainEnd;
                    cushionPointList[count + 3].Position = cush.end;

                    // cushion start normal
                    Vector3 normalStart = (cush.mainStart - cush.start) / 2;
                    normalStart = cush.mainStart - normalStart;
                    Vector3 normalEnd = normalStart + cush.startNormal * 2.5f;
                    cushionPointList[count + 4].Position = normalStart;
                    cushionPointList[count + 5].Position = normalEnd;

                    // cushion main normal
                    normalStart = (cush.mainEnd - cush.mainStart) / 2;
                    normalStart = cush.mainEnd - normalStart;
                    normalEnd = normalStart + cush.mainNormal * 10;
                    cushionPointList[count + 6].Position = normalStart;
                    cushionPointList[count + 7].Position = normalEnd;

                    // cushion end normal
                    normalStart = (cush.end - cush.mainEnd) / 2;
                    normalStart = cush.end - normalStart;
                    normalEnd = normalStart + cush.endNormal * 2.5f;
                    cushionPointList[count + 8].Position = normalStart;
                    cushionPointList[count + 9].Position = normalEnd;
                    count += 10;
                }
            }

            count = 0;
            cushionLineListIndices = new int[runtime.getState().getCushions().Length * 12];
            for (int i = 0; i < runtime.getState().getCushions().Length * 12; i += 12)
            {
                if (runtime.getState().getCushions()[i/12] != null)
                {
                    // cushion lines
                    cushionLineListIndices[i] = count;
                    cushionLineListIndices[i + 1] = count + 1;
                    cushionLineListIndices[i + 2] = count + 1;
                    cushionLineListIndices[i + 3] = count + 2;
                    cushionLineListIndices[i + 4] = count + 2;
                    cushionLineListIndices[i + 5] = count + 3;
                    //normals lines
                    cushionLineListIndices[i + 6] = count + 4;
                    cushionLineListIndices[i + 7] = count + 5;
                    cushionLineListIndices[i + 8] = count + 6;
                    cushionLineListIndices[i + 9] = count + 7;
                    cushionLineListIndices[i + 10] = count + 8;
                    cushionLineListIndices[i + 11] = count + 9;
                    count += 10;
                }
            }        
        }


        private void BuildPocketData()
        {
            pocketLineListIndices = new int[runtime.getState().getPockets().Length * 2 * slices];

            int pocketNum = 0;
            foreach (Pocket pocket in runtime.getState().getPockets())
            {
                int c = 0;
                for (int i = 0; i < slices; i++)
                {
                    pocketLineListIndices[pocketNum * slices * 2 + c] = pocketNum * slices + i;
                    if (i == slices - 1)
                    {
                        pocketLineListIndices[pocketNum * slices * 2 + c + 1] = pocketNum * slices;
                    }
                    else
                    {
                        pocketLineListIndices[pocketNum * slices * 2 + c + 1] = pocketNum * slices + i + 1;
                    }
                    c += 2;
                }
                pocketNum++;
            }

            pocketPointList = new VertexPositionColor[runtime.getState().getPockets().Length * slices];
            for (int i = 0; i < pocketPointList.Length; i++)
            {
                pocketPointList[i] = new VertexPositionColor();
                pocketPointList[i].Color = Color.Red;
            }
            
            int count = 0;
            foreach (Pocket pocket in runtime.getState().getPockets())
            {
                float step = (float)(2 * Math.PI / slices);
                float y = pocket.getPosition().Y;

                for (int i = 0; i < slices; i++)
                {
                    float x = pocket.getPosition().X + (float)(PhysicsConstants.POCKET_RADIUS / 2 * Math.Cos(step * i));
                    float z = pocket.getPosition().Z + (float)(PhysicsConstants.POCKET_RADIUS / 2 * Math.Sin(step * i));
                    pocketPointList[count * slices + i].Position = new Vector3(x, y, z);
                }
                count++;
            }

        }

        private void BuildBallData()
        {
            ballLineListIndices = new int[runtime.getState().getBalls().Length * 2 * slices];

            int ballNum = 0;
            foreach (Ball ball in runtime.getState().getBalls())
            {
                int c = 0;
                for (int i = 0; i < slices; i++)
                {
                    ballLineListIndices[ballNum * slices * 2 + c] = ballNum * slices + i;
                    if (i == slices - 1)
                    {
                        ballLineListIndices[ballNum * slices * 2 + c + 1] = ballNum * slices;
                    }
                    else
                    {
                        ballLineListIndices[ballNum * slices * 2 + c + 1] = ballNum * slices + i + 1;
                    }
                    c += 2;
                }
                ballNum++;
            }

            ballPointList = new VertexPositionColor[runtime.getState().getBalls().Length * slices];
            for (int i = 0; i < ballPointList.Length; i++)
            {
                ballPointList[i] = new VertexPositionColor();
                ballPointList[i].Color = Color.Red;
            }
            updateBallPositions();
        }

        public void updateBallPositions()
        {
            int count = 0;
            foreach (Ball ball in runtime.getState().getBalls())
            {
                float step = (float)(2 * Math.PI / slices);
                float y = ball.getPosition().Y;

                for (int i = 0; i < slices; i++)
                {
                    float x = ball.getPosition().X + (float)(PhysicsConstants.DEFAULT_RADIUS * Math.Cos(step * i));
                    float z = ball.getPosition().Z + (float)(PhysicsConstants.DEFAULT_RADIUS * Math.Sin(step * i));
                    ballPointList[count * slices + i].Position = new Vector3(x, y, z);
                }
                count++;
            }
        }

        public void GameControlPerformed(List<GameControlAction> actions)
        {

            foreach (GameControlAction action in actions)
            {
                ControlActions a = action.GetAction();
                if (a == ControlActions.DEBUG_CUSHION_EDIT_MODE)
                {
                    cushionEditMode = !cushionEditMode;
                }

                if (a == ControlActions.DEBUG_FAST_MOVE)
                {
                    fastmove = !fastmove;
                }

                if (cushionEditMode)
                {
                    if (a == ControlActions.DEBUG_1) { selectedCushion = 0; }
                    else if (a == ControlActions.DEBUG_2) { selectedCushion = 1; }
                    else if (a == ControlActions.DEBUG_3) { selectedCushion = 2; }
                    else if (a == ControlActions.DEBUG_4) { selectedCushion = 3; }
                    else if (a == ControlActions.DEBUG_5) { selectedCushion = 4; }
                    else if (a == ControlActions.DEBUG_6) { selectedCushion = 5; }
                    else if (a == ControlActions.DEBUG_7) { selectedVertex = 0; }
                    else if (a == ControlActions.DEBUG_8) { selectedVertex = 1; }
                    else if (a == ControlActions.DEBUG_9) { selectedVertex = 2; }
                    else if (a == ControlActions.DEBUG_0) { selectedVertex = 3; }
                    else
                    {
                        Cushion[] cushions = runtime.getState().getCushions();
                        Cushion cush = cushions[selectedCushion];
                        if (a == ControlActions.DEBUG_L) { 
                            updateCushion(cush, Vector3.Left, selectedVertex); 
                            BuildCushionData(); 
                        }
                        if (a == ControlActions.DEBUG_R)
                        { 
                            updateCushion(cush, Vector3.Right, selectedVertex); 
                            BuildCushionData(); 
                        }
                        if (a == ControlActions.DEBUG_U)
                        { 
                            updateCushion(cush, Vector3.Forward, selectedVertex); 
                            BuildCushionData(); 
                        }
                        if (a == ControlActions.DEBUG_D)
                        { 
                            updateCushion(cush, Vector3.Backward, selectedVertex); 
                            BuildCushionData(); 
                        }
                    }
                }
            }
        }

        private void updateCushion(Cushion cushion, Vector3 moveBy, int vertex) {
            Vector3 moveSpeed = moveBy * ((fastmove) ? 1f : 0.01f);
            switch (vertex) 
            {
                case 0:
                    cushion.start += moveSpeed;
                    break;
                case 1:
                    cushion.mainStart += moveSpeed;
                    break;
                case 2:
                    cushion.mainEnd += moveSpeed;
                    break;
                case 3:
                    cushion.end +=moveSpeed;
                    break;
            }
        }

        public void DrawTrackingLines(GameTime gameTime)
        {
            if (trackingListIndices.Length > 1)
            {
                basicEffect.World = runtime.table.getWorldMatrix();
                basicEffect.View = runtime.cameraManager.GetCamera().viewMatrix;
                basicEffect.Projection = runtime.cameraManager.GetCamera().projectionMatrix;

                basicEffect.Begin();
                foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    runtime.game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                         PrimitiveType.LineList,
                         trackingPointList,
                         0,  // vertex buffer offset to add to each element of the index buffer
                         trackingPointList.Length,  // number of vertices in pointList
                         trackingListIndices,  // the index buffer
                         0,  // first index element to read
                         trackingPointList.Length - 1   // number of primitives to draw
                     );
                    pass.End();
                }
                basicEffect.End();
            }
        }

        public void SetTrackingPoints(List<Vector3> points)
        {
            int count = 0;
            trackingPointList = new VertexPositionColor[points.Count];

            for (int i = 0; i < trackingPointList.Length; i++)
            {
                Vector3 point = points[i];
                trackingPointList[count++] = new VertexPositionColor(point, Color.White);
            }

            trackingListIndices = new int[(points.Count - 1) * 2]; 
            count = 0;

            for (int i = 0; i < points.Count - 1; i++)
            {
                trackingListIndices[count++] = i;
                trackingListIndices[count++] = i + 1;
            }
        }

    }
}

